from datetime import datetime, timedelta
import json
import logging
import os
import threading
import time
from extensions.ext_database import db
import jwt
import requests

from models.robot.area_code import SystemAreaCode
from models.robot.robots import RobotExtraInfo
from services.robot.ip_city_service import CityService


class WeatherService:
    POSTAL_CODE_MAP_FILE = "./constants/postal_code_map.json"
    LOCATION_CODE_MAP_FILE = "./constants/location_code_map.json"
    private_key:str|None = None
#     """-----BEGIN PRIVATE KEY-----
# MC4CAQAwBQYDK2VwBCIEIDgLuV8nCR5GfkjlpK5bAxJUVj7wf769II5JjrY5qBEY
# -----END PRIVATE KEY-----
# """
    HOST = "nk2k5mphwj.re.qweatherapi.com"
    CITY_LOOKUP_URL = "/geo/v2/city/lookup"
    WEATHER_BY_HOUR_URL = "/v7/weather/"
    WEATHER_WARNING_URL = "/v7/warning/now"
    WEATHER_WARNING_CITY_LIST_URL =  "/v7/warning/list"
    postal_location_code_map = None
    location_postal_code_map = None
    location_location_code_map = None
    location_code_location_map = None
    weather_map = {}
    weather_refresh_time = None
    postal_location_map = {}
    weather_warning_map = {}
    _task_thread = None
    postal_code_location_name_map = {}
    @classmethod
    def init(cls):
        if not cls.postal_code_location_name_map:
            areas = db.session.query(SystemAreaCode).all()
            for area in areas:
                cls.postal_code_location_name_map[area.postal_code] = area.name
            p_l_map = cls.get_postal_location_code_map()
            cls.location_postal_code_map = {}
            for k, v in p_l_map.items():
                cls.location_postal_code_map[v]=k
            l_l_map = cls.get_location_location_code_map()
            cls.location_code_location_map = {}
            for k,v in l_l_map.items():
                cls.location_code_location_map[v]=k
            logging.info("WeatherService Inited")


                
    @classmethod
    def get_location_name_by_postal_code(cls,postal_code)->str|None:
        location = cls.postal_code_location_name_map.get(postal_code,None)
        if not location:
            area = db.session.get(SystemAreaCode,SystemAreaCode.from_postal(postal_code))
            if area:
                location = area.name
                cls.postal_code_location_name_map[postal_code] = location
                return location
        else:
            return location
        return None


    @staticmethod
    def get_header() -> dict:
        return {
            "alg": "EdDSA",
            "kid": "KHPN4DNYXU"
        }

    @staticmethod
    def get_payload() -> dict:
        return {
            "sub": "3B899U3V3V",  # 项目id
            "iat": int(time.time())-30,
            "exp": int(time.time())+86100,
        }

    @classmethod
    def get_private_key(cls)->str:
        if cls.private_key:
            return cls.private_key
        from pathlib import Path
        current_dir = Path(__file__).resolve().parent
        file_path = current_dir / 'ed25519-private.pem'
        with open(file_path, "r") as f:
            cls.private_key = f.read()
        return cls.private_key
    
    @staticmethod
    def get_6_hours_average_weather(weather:dict|None)->dict|None:
        if not weather:
            return None
        elif weather.get("code",None)!="200":
            return None
        update_time = weather.get("updateTime","")
        update_time = datetime.fromisoformat(update_time.replace(": ",":"))
        hourly = weather.get("hourly",[])
        if not hourly or len(hourly)<6:
            return None
        i = 0
        result = {
            "begin_time":None,
            "end_time":None,
            "weathers":[],
            "wind_dirs":[],
            "min_wind_scale":None,
            "max_wind_scale":None,
            "最高气温":None,
            "最低气温":None,
        }
        for hour_weather in hourly:
            temp = hour_weather.get("temp","")
            if temp:
                if not result["最高气温"] or int(temp)> result["最高气温"]:
                    result["最高气温"] = int(temp)
                if not result["最低气温"] or int(temp)< result["最低气温"]:
                    result["最低气温"] = int(temp)
            weather = hour_weather.get("text","")
            if weather and weather not in result["weathers"]:
                result["weathers"].append(weather)
            wind_scale = hour_weather.get("windScale","")
            if wind_scale:
                winds = wind_scale.split("-")
                if len(winds) == 2:
                    if not result["min_wind_scale"] or int(winds[0]) < result["min_wind_scale"]:
                        result["min_wind_scale"] = int(winds[0])
                    if not result["max_wind_scale"] or int(winds[1]) > result["max_wind_scale"]:
                        result["max_wind_scale"] = int(winds[1])
            wind_dir = hour_weather.get("windDir",None)
            if wind_dir:
                if wind_dir not in result["wind_dirs"]:
                    result["wind_dirs"].append(wind_dir)
            i += 1
            if i ==1 :
                fxTime = hour_weather.get("fxTime",None)
                result["begin_time"] = fxTime
            
            if i >= 6:
                fxTime = hour_weather.get("fxTime",None)
                result["end_time"] = fxTime
                break
        return result

        
    @classmethod
    def weather_buffer_task(cls,postal_codes: list[str]):
        cls.weather_map.clear()
        for postal_code in postal_codes:
            weather = cls.get_weather_by_24hours(postal_code)
            weather = cls.get_6_hours_average_weather(weather)
            if weather:
                cls.weather_map[postal_code] = weather
        warning_city_list = cls.get_weather_warning_city_list()
        warning_city_list = warning_city_list.get("cityList",[])
        cls.weather_warning_map.clear()
        for postal_code in postal_codes:
            location_code,location_name = cls.get_location_code_by_postal(postal_code)
            if not location_code:
                continue
            if location_code in warning_city_list:
                cls.weather_warning_map[postal_code] = cls.get_weather_warning(postal_code)
        cls._task_thread = None
        cls.weather_refresh_time = datetime.now()+ timedelta(hours=6)

    @classmethod
    def get_weather_by_robot(cls,robot_id,location:str|None,ip_addr)->dict|None:
        if location:
            location_code,postal_code = cls.get_location_code_by_location(location)
            if not location_code:
                return None
            if postal_code and postal_code in cls.weather_map:
                return cls.weather_map[postal_code]
            else:
                weather = cls.get_weather_by_24hours(location_code,False)
                weather = cls.get_6_hours_average_weather(weather)
            if weather:
                weather["location"]=location
                if postal_code:
                    cls.weather_map[postal_code] = weather
                return weather
        
        # extral_info = db.session.query(RobotExtraInfo).filter(RobotExtraInfo.id.ilike(f"{robot_id}%")).first()
        postal_code = db.session.query(RobotExtraInfo.postal_code).filter(RobotExtraInfo.id==robot_id).scalar()
        if postal_code :
            if postal_code in cls.weather_map:
                weather = cls.weather_map[postal_code]
            else:
                weather = cls.get_weather_by_24hours(postal_code,True)
                weather = cls.get_6_hours_average_weather(weather)
            if weather:
                location = cls.get_location_name_by_postal_code(postal_code)
                if location:
                    weather["location"] = location
                cls.weather_map[postal_code] = weather
                return weather

        if ip_addr:
            result = CityService.get_city_by_ip(ip_addr)
            if result:
                postal_code = result.get("cityCode",None)
                if postal_code:
                    if postal_code in cls.weather_map:
                        return cls.weather_map[postal_code]
                    else:
                        weather = cls.get_weather_by_24hours(postal_code,True)
                        weather = cls.get_6_hours_average_weather(weather)
                    if weather:
                        location = cls.get_location_name_by_postal_code(postal_code)
                        if location:
                            weather["location"] = location
                        cls.weather_map[postal_code] = weather
                        return weather
        return None

    @classmethod
    def get_weather_by_postal(cls, postal_code: str)->dict|None:
        weather = cls.weather_map.get(postal_code,None)
        if not weather:
            weather = cls.get_weather_by_24hours(postal_code)
            weather = cls.get_6_hours_average_weather(weather)
            if weather:
                cls.weather_map[postal_code] = weather
        if not cls.weather_refresh_time or cls.weather_refresh_time < datetime.now():
            cls.buffer_task()
        return weather
        

    @classmethod
    def buffer_task(cls,postal_codes: list[str]|None=None):
        if cls._task_thread:
            return
        if not postal_codes:
            infos = db.session.query(RobotExtraInfo.postal_code).distinct(RobotExtraInfo.postal_code).all()
            postal_codes = [info[0] for info in infos]
        cls._task_thread = threading.Thread(target=cls.weather_buffer_task,name="WeatherBufferingTask",args=(postal_codes,))
        cls._task_thread.start()
    
    @classmethod
    def encode_jwt(cls) -> str:
        private_key = cls.get_private_key()
        encoded_jwt = jwt.encode(WeatherService.get_payload(
        ), private_key, algorithm="EdDSA", headers=cls.get_header())
        return encoded_jwt

    # @classmethod
    # def get_location_id_by_postal_code(cls, postal_code: str) -> str:


    @classmethod
    def get_weather_by_24hours(cls, postal_code: str,is_postal_code:bool=True) -> dict:
        if is_postal_code:
            location_code,_ = cls.get_location_code_by_postal(postal_code)
            if not location_code:
                return {"code":"400","message":"Invalid Postal Code"}
        else:
            location_code = postal_code
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        url = f"https://{cls.HOST}{cls.WEATHER_BY_HOUR_URL}/24h"
        args = {
            "location": location_code
        }
        response = requests.get(url, headers=header, params=args)
        return response.json()
    
    @classmethod
    def get_weather_warning(cls, location_code: str) -> dict:
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        url = f"https://{cls.HOST}{cls.WEATHER_WARNING_URL}"
        args = {
            "location": location_code
        }
        response = requests.get(url, headers=header, params=args)
        return response.json()    
    
    @classmethod
    def get_weather_warning_city_list(cls) -> dict:
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        args = {
            "range": "cn"
        }
        url = f"https://{cls.HOST}{cls.WEATHER_WARNING_CITY_LIST_URL}"
        response = requests.get(url, headers=header, params=args)
        json_data= response.json()
        if json_data["code"] != "200":
            return {"cityList":[]}
        result = {
            "updateTime":json_data["updateTime"],
            "cityList":[]
            }
        warningLocList = json_data.get("warningLocList",[])
        for warningLoc in warningLocList:
            location_id = warningLoc.get("locationId",None)
            result["cityList"].append(location_id)
        return result

    
    @classmethod
    def get_weather_now(cls, postal_code: str) -> dict:
        location_code,_ = cls.get_location_code_by_postal(postal_code)
        if not location_code:
            return {"code":"400","message":"Invalid Postal Code"}
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        url = f"https://{cls.HOST}{cls.WEATHER_BY_HOUR_URL}/now"
        args = {
            "location": location_code
        }
        response = requests.get(url, headers=header, params=args)
        return response.json()
    
    @classmethod
    def get_weather_3days(cls, postal_code: str) -> dict:
        location_code,_ = cls.get_location_code_by_postal(postal_code)
        if not location_code:
            return {"code":"400","message":"Invalid Postal Code"}
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        url = f"https://{cls.HOST}{cls.WEATHER_BY_HOUR_URL}/3d"
        args = {
            "location": location_code
        }
        response = requests.get(url, headers=header, params=args)
        return response.json()    
    
    @classmethod
    def get_location_code_by_postal(cls,postal_code: str)->tuple[str|None,str|None]:
        location_map = cls.get_postal_location_code_map()
        location_id = None
        location_name = None
        if location_map:
            location_id = location_map.get(postal_code,None)
        if not location_id:
            _,location_id,_,location_name = cls.get_city_code(postal_code,None)
        return location_id,location_name
    

    @classmethod
    def get_location_code_by_location(cls,location: str)->tuple[str|None,str|None]:
        location_map = cls.get_location_location_code_map()
        location_id = None
        postal_code = None
        if location_map:
            location_id = location_map.get(location,None)
        if not location_id:
            _,location_id,postal_code,_ = cls.get_city_code(None,location)
        else:
            postal_code = cls.get_postal_code_by_location_code(location_id)
        
        return location_id,postal_code
    
    @classmethod
    def get_postal_code_by_location_code(cls,location_code:str)->str|None:
        postal_code = None
        if cls.location_postal_code_map:
            postal_code = cls.location_postal_code_map.get(location_code,None)
        return postal_code
        
    @classmethod
    def get_city_code(cls,postal_code:str|None, location:str|None) -> tuple[dict|None,str|None,str|None,str|None]:
        header = {
            "Authorization": "Bearer " + cls.encode_jwt()
        }
        url = f"https://{cls.HOST}{cls.CITY_LOOKUP_URL}"
        args = {
            "location": postal_code or location
        }
        print("URL:", url)
        print("headers:", header)
        response = requests.get(url, headers=header, params=args)
        if response.status_code != 200:
            # raise Exception("Failed to get city code")
            return None,None,None,None
        result = response.json()
        postal_code = postal_code
        location_name = location
        if result.get("code", None) == "200":
            if len(result.get("location", [])) > 0:
                location_code = result.get("location")[0].get("id")
                if location_code:
                    if location:
                        area = db.session.query(SystemAreaCode).filter(SystemAreaCode.name.ilike(f"%{location}%")).first()
                        if area:
                            cls.save_postal_location_code(area.code, location_code)
                            postal_code = area.code
                        cls.save_location_location_code(location, location_code)
                elif postal_code:
                    area = db.session.get(SystemAreaCode,SystemAreaCode.from_postal(postal_code))
                    if area:
                        cls.save_location_location_code(area.name,location_code)
                        location_name = area.name
                    cls.save_postal_location_code(postal_code,location_code)
                    postal_code = postal_code
        return result,location_code,postal_code,location_name

    @classmethod
    def save_postal_location_code(cls, postal_code: str, location_code: str):
        location_code_map = cls.get_postal_location_code_map()
        if location_code_map is None:
            location_code_map = {}
        if postal_code not in location_code_map:
            location_code_map[postal_code] = location_code
            with open(cls.POSTAL_CODE_MAP_FILE, "w", encoding="utf-8") as f:
                json.dump(location_code_map, f, ensure_ascii=False, indent=4)

    @classmethod
    def save_location_location_code(cls, location: str, location_code: str):
        location_code_map = cls.get_location_location_code_map()
        if location_code_map is None:
            location_code_map = {}
        if location not in location_code_map:
            location_code_map[location] = location_code
            with open(cls.LOCATION_CODE_MAP_FILE, "w", encoding="utf-8") as f:
                json.dump(location_code_map, f, ensure_ascii=False, indent=4)

    @classmethod
    def get_postal_location_code_map(cls, file_path: str | None = None) -> dict:
        """
        从 JSON 文件加载配置并返回字典

        参数:
            file_path: JSON 文件路径

        返回:
            包含配置的字典
        """
        if not file_path and cls.postal_location_code_map:
            return cls.postal_location_code_map
        else:
            file_path = cls.POSTAL_CODE_MAP_FILE
        if not os.path.exists(file_path):
            return {}
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                cls.postal_location_code_map = json.load(f)
                return cls.postal_location_code_map
        except FileNotFoundError:
            logging.error(f"配置文件 {file_path} 不存在")
            # raise FileNotFoundError(f"配置文件 {file_path} 不存在")
        except json.JSONDecodeError:
            logging.error(f"配置文件 {file_path} 错误")
            # raise ValueError(f"配置文件 {file_path} 格式错误，不是有效的 JSON")
        return {}


    @classmethod
    def get_location_location_code_map(cls, file_path: str | None = None) -> dict:
        """
        从 JSON 文件加载配置并返回字典

        参数:
            file_path: JSON 文件路径

        返回:
            包含配置的字典
        """
        if not file_path and cls.location_location_code_map:
            return cls.location_location_code_map
        else:
            file_path = cls.LOCATION_CODE_MAP_FILE
        if not os.path.exists(file_path):
            return {}
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                cls.location_location_code_map = json.load(f)
                return cls.location_location_code_map
        except FileNotFoundError:
            logging.error(f"配置文件 {file_path} 不存在")
            # raise FileNotFoundError(f"配置文件 {file_path} 不存在")
        except json.JSONDecodeError:
            logging.error(f"配置文件 {file_path} 格式错误，不是有效的 JSON")
            # raise ValueError(f"配置文件 {file_path} 格式错误，不是有效的 JSON")
        return {}

if __name__ == "__main__":
    # print(WeatherService.get_city_code("上海"))#101020100
    # print(WeatherService.get_weather_by_24hours("101020100"))
    # code_map = WeatherService.get_location_code_map()
    print(WeatherService.get_weather_by_postal("上海"))
    # print(code_map)
    # WeatherService.get_city_code("420105")
    # print(WeatherService.get_weather_now("101020100"))

##### 当前天气now
# {
#     "code": "200",
#     "updateTime": "2025-06-26T16:19+08:00",
#     "fxLink": "https://www.qweather.com/weather/shanghai-101020100.html",
#     "now": {
#         "obsTime": "2025-06-26T16:16+08:00",
#         "temp": "34",
#         "feelsLike": "39",
#         "icon": "101",
#         "text": "多云",
#         "wind360": "45",
#         "windDir": "东北风",
#         "windScale": "1",
#         "windSpeed": "4",
#         "humidity": "62",
#         "precip": "0.0",
#         "pressure": "1006",
#         "vis": "13",
#         "cloud": "91",
#         "dew": "25"
#     },
#     "refer": {
#         "sources": [
#             "QWeather"
#         ],
#         "license": [
#             "QWeather Developers License"
#         ]
#     }
# }


##
# -----------------------------------------------
## 24小时天气
# {
#     "code": "200",
#     "updateTime": "2025-06-26T16: 02+08: 00",
#     "fxLink": "https: //www.qweather.com/weather/shanghai-101020100.html",
#     "hourly": [
#         {
#             "fxTime": "2025-06-26T17:00+08:00",
#             "temp": "34",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "49",
#             "windDir": "东北风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "68",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "85",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-26T18:00+08:00",
#             "temp": "32",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "57",
#             "windDir": "东北风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "72",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "57",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-26T19:00+08:00",
#             "temp": "31",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "97",
#             "windDir": "东风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "78",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "29",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-26T20:00+08:00",
#             "temp": "29",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "125",
#             "windDir": "东南风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "83",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "1",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-26T21:00+08:00",
#             "temp": "29",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "133",
#             "windDir": "东南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "83",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "34",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-26T22:00+08:00",
#             "temp": "29",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "143",
#             "windDir": "东南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "85",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "66",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-26T23:00+08:00",
#             "temp": "29",
#             "icon": "151",
#             "text": "多云",
#             "wind360": "152",
#             "windDir": "东南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "85",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "99",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T00:00+08:00",
#             "temp": "28",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "158",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "86",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "94",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T01:00+08:00",
#             "temp": "28",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "164",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "87",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "89",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T02:00+08:00",
#             "temp": "28",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "168",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "87",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "85",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T03:00+08:00",
#             "temp": "28",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "169",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "87",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "56",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T04:00+08:00",
#             "temp": "28",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "170",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "87",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "28",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T05:00+08:00",
#             "temp": "27",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "170",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "91",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "0",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T06:00+08:00",
#             "temp": "29",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "168",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "7",
#             "humidity": "86",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "0",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T07:00+08:00",
#             "temp": "29",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "162",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "7",
#             "humidity": "83",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "0",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T08:00+08:00",
#             "temp": "30",
#             "icon": "104",
#             "text": "阴",
#             "wind360": "155",
#             "windDir": "东南风",
#             "windScale": "1-3",
#             "windSpeed": "7",
#             "humidity": "77",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1005",
#             "cloud": "0",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T09:00+08:00",
#             "temp": "32",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "175",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "7",
#             "humidity": "71",
#             "pop": "1",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "4",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T10:00+08:00",
#             "temp": "33",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "196",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "9",
#             "humidity": "66",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "9",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T11:00+08:00",
#             "temp": "35",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "200",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "61",
#             "pop": "2",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "13",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T12:00+08:00",
#             "temp": "36",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "201",
#             "windDir": "南风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "59",
#             "pop": "4",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "15",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T13:00+08:00",
#             "temp": "36",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "17",
#             "windDir": "北风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "56",
#             "pop": "8",
#             "precip": "0.0",
#             "pressure": "1007",
#             "cloud": "17",
#             "dew": "25"
#         },
#         {
#             "fxTime": "2025-06-27T14:00+08:00",
#             "temp": "37",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "26",
#             "windDir": "东北风",
#             "windScale": "1-3",
#             "windSpeed": "11",
#             "humidity": "53",
#             "pop": "8",
#             "precip": "0.0",
#             "pressure": "1007",
#             "cloud": "18",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T15:00+08:00",
#             "temp": "36",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "40",
#             "windDir": "东北风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "56",
#             "pop": "12",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "30",
#             "dew": "24"
#         },
#         {
#             "fxTime": "2025-06-27T16:00+08:00",
#             "temp": "35",
#             "icon": "101",
#             "text": "多云",
#             "wind360": "50",
#             "windDir": "东北风",
#             "windScale": "1-3",
#             "windSpeed": "13",
#             "humidity": "58",
#             "pop": "51",
#             "precip": "0.0",
#             "pressure": "1006",
#             "cloud": "70",
#             "dew": "24"
#         }
#     ],
#     "refer": {
#         "sources": [
#             "QWeather"
#         ],
#         "license": [
#             "QWeather Developers License"
#         ]
#     }
# }